using System;
using System.Collections.Generic;
using JetBrains.Annotations;

namespace DynamicProxyCore
{
	/// <summary>
	/// Various collection utils
	/// </summary>
	public static class CollectionUtils
	{
		/// <summary>
		/// Determines existance of element for any IEnumerable type
		/// </summary>
		/// <typeparam name="T">collection item type</typeparam>
		/// <param name="l">collection to be searched</param>
		/// <param name="pred">predicate that represents match condition </param>
		/// <returns>true if item exist</returns>
		public static bool Exist<T>(IEnumerable<T> l, Func<T, bool> pred)
		{
			return IndexOf(l, pred) >= 0;
		}
		/// <summary>
		/// Finds index of element for any IEnumerable
		/// </summary>
		/// <typeparam name="T">collection item type </typeparam>
		/// <param name="l">Collection to be searched1</param>
		/// <param name="pred">match condition</param>
		/// <returns>Index of item or -1 if not found</returns>
		public static int IndexOf<T>(IEnumerable<T> l, Func<T, bool> pred)
		{
			int i = 0;
			foreach(T t in l)
			{
				if(pred(t)) return i;
				i++;
			}

			return -1;
		}

		public static IEnumerable<T> Add<T>(this IEnumerable<T> collection, T newItem)
		{
			foreach (var item in collection)
			{
				yield return item;
			}
			yield return newItem;
		}

		public static TValue TryGet<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, [NotNull] TKey key, [NotNull] Func<TValue> defaultValue)
		{
			TValue value;
			if(!dictionary.TryGetValue(key, out value))
			{
				value = dictionary[key] = defaultValue();
			}
			return value;
		}

		public static TValue TryGet<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue))
		{
			return dictionary.TryGet(key, () => defaultValue);
		}

		/// <summary>
		/// Foreach method for any IEnumerable type
		/// </summary>
		/// <typeparam name="T">item type</typeparam>
		/// <param name="list">list to be iterated</param>
		/// <param name="action">action to be executed for each items</param>
		public static void ForEach<T>(IEnumerable<T> list, Action<T> action)
		{ foreach(T t in list) { action(t); } }



		/// <summary>
		/// Pops items in Stack up to specified item
		/// Calls <paramref name="action"/> at for evry poped item
		/// </summary>
		/// <typeparam name="T">Stack items type</typeparam>
		/// <param name="stack">Stack to be used</param>
		/// <param name="including">Item at which poping stops</param>
		/// <param name="action">action to be executed for popped item</param>
		/// <returns> number of items popped</returns>
		public static int PopIncluding<T>(Stack<T> stack, T including, Action<T> action)
		{
			int count = 0;
			do
			{
				count++;
				action(stack.Peek());

			} while(!stack.Pop().Equals(including) && stack.Count > 0);
			return count;
		}


		/// <summary>
		/// Adds item to the hashset(if there is no existing item in this hashset)
		/// </summary>
		/// <typeparam name="T">item type</typeparam>
		/// <param name="hashSet">HashSet to be iterated</param>
		/// <param name="item">item to add</param>
		public static void AddIfNotExists<T>(this HashSet<T> hashSet, T item)
		{
			if (!hashSet.Contains(item))
			{
				hashSet.Add(item);
			}
		}


		/// <summary>Creates a <see cref="T:System.Collections.Generic.HashSet`1" /> from an <see cref="T:System.Collections.Generic.IEnumerable`1" />.</summary>
		/// <typeparam name="TSource">The type of the elements of source.</typeparam>
		/// <param name="source">The <see cref="T:System.Collections.Generic.IEnumerable`1" /> to create a <see cref="T:System.Collections.Generic.HashSet`1" /> from.</param>
		/// <returns>A <see cref="T:System.Collections.Generic.HashSet`1" /> that contains elements from the input sequence. </returns>
		public static HashSet<TSource> ToHashSet<TSource>(this IEnumerable<TSource> source)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			return new HashSet<TSource>(source);
		}

		/// <summary>Creates a <see cref="T:System.Collections.Generic.HashSet`1" /> from an <see cref="T:System.Collections.Generic.IEnumerable`1" />.</summary>
		/// <typeparam name="TSource">The type of the elements of source.</typeparam>
		/// <param name="source">The <see cref="T:System.Collections.Generic.IEnumerable`1" /> to create a <see cref="T:System.Collections.Generic.HashSet`1" /> from.</param>
		/// <param name="checkUnique">Checks all items for the unique, before adding to result.</param>
		/// <returns>A <see cref="T:System.Collections.Generic.HashSet`1" /> that contains elements from the input sequence. </returns>
		public static HashSet<TSource> ToHashSet<TSource>(this IEnumerable<TSource> source, bool checkUnique)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			if (!checkUnique)
			{
				return source.ToHashSet();
			}
			
			var result = new HashSet<TSource>();
			foreach (var sourceItem in source)
			{
				if (!result.Contains(sourceItem))
				{
					result.Add(sourceItem);
				}			
			}

			return result;
		}
	}
}